// *** LICENSE HEADER ***
// Filename: ./connector.cpp
// This file is part of Nodewarz 0.1
// Nodewarz is a 3d OpenGL strategy game
// Copyright (C) 2007 CZestmyr
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *** END LICENSE HEADER ***
#include "connector.h"

connector::connector(engine* par):sr(par->GetLogger()) {
    isServer = true;
    isInit = false;
    isClosed = false;
    parent = par;
    localName[0] = 0x00;
    srand(time(NULL));
    //Pick a funny (or maybe not so funny) initial client name
    switch (rand()%10){
        case 0:
            strcpy_safe_my(localName, "Mr. Mao", 33);
        break;
        case 1:
            strcpy_safe_my(localName, "Mr. Adolf", 33);
        break;
        case 2:
            strcpy_safe_my(localName, "Mr. Che", 33);
        break;
        case 3:
            strcpy_safe_my(localName, "Mr. Saddam", 33);
        break;
        case 4:
            strcpy_safe_my(localName, "Mr. Josef", 33);
        break;
        case 5:
            strcpy_safe_my(localName, "Mr. Vladimir", 33);
        break;
        case 6:
            strcpy_safe_my(localName, "Mr. Genghis", 33);
        break;
        case 7:
            strcpy_safe_my(localName, "Mr. Napoleon", 33);
        break;
        case 8:
            strcpy_safe_my(localName, "Mr. Atilla", 33);
        break;
        case 9:
            strcpy_safe_my(localName, "Mr. Ho", 33);
        break;
    }
}

connector::~connector() {}

bool connector::Init(Uint16 port) {
    if (isInit) return false;

    if (!sr.Init(port))
        return false;

    //Client/server data initialization
    if (isServer){
        for (int i = 0; i < 4; ++i) {
             connected[i] = false;
        }
    } else {
        clientConnected = false;
    }

    isInit = true;

    return true;
}

void connector::Deinit(){
    if (!isInit) return;

    //If server, disconnect all clients
    if (isServer){
        for (int i = 0; i < 4; ++i)
            if (connected[i]) {
                Disconnect(i);
            }
    } else if (clientConnected){
        Disconnect();
    }

    //Try to send all packets in queue
    sr.TryToSendWholeQueue();

    isInit = false;
    sr.Deinit();
}

bool connector::IsInit(){
    return isInit;
}

bool connector::IsServer(){
    return isServer;
}


void connector::Update(Sint32 dt){
    if (!isInit) return;

    //Message receiving
    int messageNum = sr.Receive();

    IPaddress remoteip;

    for (int i = 0; i < messageNum; ++i){
        sr.GetMessage(remoteip.host, remoteip.port, messageBuffer);


        //Log the received message
        char buf[128];
        parent->GetLogger()->log(LL_DEBUG, "Incoming network message:");
        sprintf(buf, "%i.%i.%i.%i:%i", ((char*)&(remoteip.host))[0]
                                     , ((char*)&(remoteip.host))[1]
                                     , ((char*)&(remoteip.host))[2]
                                     , ((char*)&(remoteip.host))[3]
                                     , SDLNet_Read16(&remoteip.port));
        parent->GetLogger()->log(LL_DEBUG, buf);
        parent->GetLogger()->log(LL_DEBUG, messageBuffer);


        if (isServer){
            int j;
            //Find out the message type
            switch (messageBuffer[0] - '0'){
                case _CONNECTOR_HI:
                    if (IsFull() || isClosed) {
                        sprintf(outgoingMessageBuffer, "%i", _CONNECTOR_FULL);
                        sr.Send(remoteip.host, remoteip.port, outgoingMessageBuffer);
                    } else {
                        sprintf(outgoingMessageBuffer, "%i", _CONNECTOR_HI);
                        sr.Send(remoteip.host, remoteip.port, outgoingMessageBuffer);
                        int pos = GetFirstFreeSlotIndex();
                        players[pos] = remoteip;
                        connected[pos] = true;
                        strcpy(clientNames[pos], "NN");
                        timeSinceLastHeartbeat[pos] = 0;
                        SendClientList();
                        SendClientNameList();
                    }
                break;
                case _CONNECTOR_BYE:
                    for (j = 0; j < 4; ++j)
                        if (players[j] == remoteip){
                            connected[j] = false;
                            parent->ClientDisconnected(j);
                            break;
                        }
                    if (j > 4) break;
                    SendClientList();
                    SendClientNameList();
                break;
                case _CONNECTOR_HBT:
                    for (j = 0; j < 4; ++j)
                        if (players[j] == remoteip)
                            timeSinceLastHeartbeat[j] = 0;
                break;
                case _CONNECTOR_MSG:
                    parent->GetMessageViewer()->AddMessage(&messageBuffer[1]);
                    //Send to other clients
                    for (j = 0; j < 4; ++j)
                        if (players[j] == remoteip) break;
                    Relay(j);
                break;
                case _CONNECTOR_EVT:
                    //Just resend the event to clients
                    for (j = 0; j < 4; ++j)
                        if (players[j] == remoteip) break;
                    Relay(j);
                break;
            }
        } else {
            //Find out the message type (this time for client)
            switch (messageBuffer[0] - '0'){
                case _CONNECTOR_HI:
                    if (awaitingServerResponse){
                        awaitingServerResponse = false;
                        clientConnected = true;
                    }
                break;
                case _CONNECTOR_BYE:
                    clientConnected = false;
                    Deinit();
                    parent->ServerDisconnected();
                break;
                case _CONNECTOR_MSG:
                    //View the message
                    parent->GetMessageViewer()->AddMessage(&messageBuffer[1]);
                break;
                case _CONNECTOR_FULL:
                    if (awaitingServerResponse){
                        awaitingServerResponse = false;
                        clientConnected = false;
                    }
                break;
            }
        }

        //Event processing
        if (messageBuffer[0] - '0' == _CONNECTOR_EVT){
            //Separate processing of server-only events (non-game-related)
            if (isServer){
                char intBuffer[6];
                strcpy_safe_my(intBuffer, messageBuffer+1, 6);
                //Switch event type
                switch(atoi(intBuffer)){
                    case _CONNECTOR_EVENT_CHANGENAME:
                        for (int j = 0; j < 4; ++j){
                            if (players[j] == remoteip && connected[j])
                                strcpy_safe_my(clientNames[j], messageBuffer+6, 33);
                        }
                        SendClientNameList();
                    break;
                }
            }

            //All the events are handled within the engine
            if (eventHandlerCallback){
                eventHandlerCallback(parent, messageBuffer);
            }
        }
    }

    //Heartbeats handling
    if (isServer) {
        for (int i = 0; i < 4; ++i){
            if (connected[i]){
                timeSinceLastHeartbeat[i] += dt;
                //Client didn't send a heartbeat for a long time. Forget about it
                if (timeSinceLastHeartbeat[i] > _CONNECTOR_HEARTBEAT_TIMEOUT){
                    connected[i] = false;
                    parent->ClientDisconnected(i);
                }
            }
        }
    } else {
        //If client, send the message when needed
        lastHeartbeat += dt;
        if (clientConnected && lastHeartbeat > _CONNECTOR_HEARTBEAT_DELAY) {
            SendHeartbeat();
            lastHeartbeat -= _CONNECTOR_HEARTBEAT_DELAY;
        }
    }

    //Update the sender-receiver
    sr.Update(dt);
}

void connector::SetServer(bool newIsServer){
    if (isInit) return;
    isServer = newIsServer;
}

void connector::SetEventCallback(void (&newCallback)(engine*, const char*)){
    eventHandlerCallback = newCallback;
}

void connector::Close(){
    isClosed = true;
}

void connector::Open(){
    isClosed = false;
}

void connector::Disconnect(int clientNum){
    if (!isInit) return;

    sprintf(outgoingMessageBuffer, "%i", _CONNECTOR_BYE);
    if (clientNum == -1) {
        sr.Send(server.host, server.port, outgoingMessageBuffer);
    }
    else {
        if (clientNum >= 0 && clientNum < 4 && connected[clientNum]) {
        sr.Send(players[clientNum].host, players[clientNum].port, outgoingMessageBuffer);
        connected[clientNum] = false;

        SendClientList();
        SendClientNameList();
        }
    }
}

bool connector::Connect(IPaddress newServer){
    if (!isInit) return false;

    server = newServer;
    sprintf(outgoingMessageBuffer, "%i", _CONNECTOR_HI);
    sr.Send(server.host, server.port, outgoingMessageBuffer);

    parent->GetLogger()->log(LL_DEBUG, "Client connecting:");
    parent->GetLogger()->log(LL_DEBUG, outgoingMessageBuffer);

    awaitingServerResponse = true;
    lastHeartbeat = 0;
    return true;
}

int connector::IsConnected(){
    if (!isInit) return 0;

    if (awaitingServerResponse)
        return -1;
    if (clientConnected)
        return 1;
    else return 0;
}

void connector::SendMessage(const char* message, int dontSendHere){
    if (!isInit) return;

    //Append client name in front of the message
    sprintf(outgoingMessageBuffer, "%i", _CONNECTOR_MSG);
    int localNameLength = strlen(localName);
    if (localNameLength != 0) {
        strcpy_safe_my(&outgoingMessageBuffer[1], localName, _SEREC_MAX_MESSAGE_LENGTH - 1);
        outgoingMessageBuffer[localNameLength+1] = ':';
        outgoingMessageBuffer[localNameLength+2] = ' ';
        strcpy_safe_my(&outgoingMessageBuffer[localNameLength+3], message, _SEREC_MAX_MESSAGE_LENGTH - localNameLength - 2);
    } else
        strcpy_safe_my(&outgoingMessageBuffer[1], message, _SEREC_MAX_MESSAGE_LENGTH - 1);

    //Send the message either to all clients (server) or to the server
    if (isServer) {
        for (int i = 0; i < 4; ++i)
        if (connected[i] && i != dontSendHere)
        sr.Send(players[i].host, players[i].port, outgoingMessageBuffer);
    }
    else {
        sr.Send(server.host, server.port, outgoingMessageBuffer);
    }

    //Set the message on the local machine as well
    parent->GetMessageViewer()->AddMessage(&outgoingMessageBuffer[1]);
}

void connector::PrepareEvent(int eventtype){
    char integerBuffer[32];

    //Copy the event type data into the message buffer
    sprintf(integerBuffer, "%i", _CONNECTOR_EVT);
    sprintf(&integerBuffer[1], "%5i", eventtype);
    strcpy_safe_my(outgoingMessageBuffer, integerBuffer, 7);
    messageBufferPointer = 6;
}

void connector::AppendEventDataI(int integer){
    //Message buffer overflow
    if (messageBufferPointer + 13 > _SEREC_MAX_MESSAGE_LENGTH)
        return;

    char integerBuffer[32];

    sprintf(integerBuffer, "%12i", integer);
    strcpy_safe_my(&outgoingMessageBuffer[messageBufferPointer], integerBuffer, 13);
    messageBufferPointer += 12;
}

void connector::AppendEventDataF(float floatingpoint){
    //Message buffer overflow
    if (messageBufferPointer + 13 > _SEREC_MAX_MESSAGE_LENGTH)
        return;

    char floatBuffer[32];

    sprintf(floatBuffer, "%12f", floatingpoint);
    strcpy_safe_my(&outgoingMessageBuffer[messageBufferPointer], floatBuffer, 13);
    messageBufferPointer += 12;
}

void connector::AppendEventDataS(const char* str){
    strcpy_safe_my(&outgoingMessageBuffer[messageBufferPointer], str, _SEREC_MAX_MESSAGE_LENGTH - messageBufferPointer);
}

void connector::SendEvent(int dontSendHere){
    if (!isInit) return;

    if (isServer) {
        for (int i = 0; i < 4; ++i)
        if (connected[i] && i != dontSendHere)
        sr.Send(players[i].host, players[i].port, outgoingMessageBuffer);
    }
    else {
        sr.Send(server.host, server.port, outgoingMessageBuffer);
    }
}

void connector::SendEventTo(int sendHere){
    if (!isInit) return;

    if (isServer)
        if (connected[sendHere])
            sr.Send(players[sendHere].host, players[sendHere].port, outgoingMessageBuffer);
}

void connector::SetLocalName(const char* newName){
    strcpy_safe_my(localName, newName, 33);

    if (isInit){
        if (isServer){
            SendClientNameList();
        } else {
            PrepareEvent(_CONNECTOR_EVENT_CHANGENAME);
            AppendEventDataS(localName);
            SendEvent();
        }
    }
}

const char* connector::GetLocalName(){
    return localName;
}

const char* connector::GetClientName(int clientnum){
    Constrainti(clientnum, 0, 3);
    return clientNames[clientnum];
}

void connector::SendHeartbeat(){
    sprintf(outgoingMessageBuffer, "%i", _CONNECTOR_HBT);
    sr.Send(server.host, server.port, outgoingMessageBuffer);
}

bool connector::IsFull(){
    for (int i = 0; i < 4; ++i)
        if (!connected[i]) return false;
    return true;
}

int connector::GetFirstFreeSlotIndex(){
    for (int i = 0; i < 4; ++i)
        if (!connected[i]) return i;
}

void connector::SendClientList(){
    char ipbuffer[32];

    PrepareEvent(_CONNECTOR_EVENT_CLIENTLIST);
    strcpy(&outgoingMessageBuffer[messageBufferPointer], "server#");
    messageBufferPointer += 7;
    for (int i = 0; i < 4; ++i){
        if (!connected[i]) continue;
        sprintf(ipbuffer, "%i.%i.%i.%i:%i#", ((char*)&(players[i].host))[0],
                                            ((char*)&(players[i].host))[1],
                                            ((char*)&(players[i].host))[2],
                                            ((char*)&(players[i].host))[3],
                                            SDLNet_Read16(&players[i].port));
        strcpy_safe_my(&outgoingMessageBuffer[messageBufferPointer], ipbuffer, strlen(ipbuffer)+1);
        messageBufferPointer += strlen(ipbuffer);
    }
    SendEvent();

    //Also update the server's client list
    parent->GetRoster()->GetGui()->SetElementText(outgoingMessageBuffer + 6, _BUTTON_CLIENTLIST);
}

void connector::SendClientNameList(){
    PrepareEvent(_CONNECTOR_EVENT_CLIENTNAMELIST);
    strcpy_safe_my(&outgoingMessageBuffer[messageBufferPointer], localName, strlen(localName)+1);
    messageBufferPointer += strlen(localName);
    outgoingMessageBuffer[messageBufferPointer] = '#'; //The hash mark serves as a newline sign
    ++messageBufferPointer;
    outgoingMessageBuffer[messageBufferPointer] = 0x00;


    for (int i = 0; i < 4; ++i){
        if (!connected[i]) continue;
        strcpy_safe_my(&outgoingMessageBuffer[messageBufferPointer], clientNames[i], strlen(clientNames[i])+1);
        messageBufferPointer += strlen(clientNames[i]);
        outgoingMessageBuffer[messageBufferPointer] = '#';
        ++messageBufferPointer;
        outgoingMessageBuffer[messageBufferPointer] = 0x00;
    }
    SendEvent();

    //Also update the server's client name list
    parent->GetRoster()->GetGui()->SetElementText(outgoingMessageBuffer + 6, _BUTTON_NAMELIST);

    //And update the server's kick buttons
    parent->GetRoster()->UpdateKickButtons(connected);
}

int connector::SetNextClientNumber(player* pl){
    int previousClientNumber;
    if (pl->GetType() == PT_LOCAL)
        previousClientNumber = -1;
    else
        previousClientNumber = pl->GetClientNumber();

    int i;
    for (i = previousClientNumber + 1; i < 4; ++i)
        if (connected[i]) break;

    if (i >= 4) {
        i = -1;
        pl->SetType(PT_LOCAL);
    } else {
        pl->SetClientNumber(i);
        pl->SetType(PT_REMOTE);
    }

    return i;
}

void connector::UpdateClientNumber(player* pl){
    if (!connected[pl->GetClientNumber()])
        SetNextClientNumber(pl);
}

void connector::Relay(int dontSendHere){
    for (int i = 0; i < 4; ++i)
        if (connected[i] && i != dontSendHere)
        sr.Send(players[i].host, players[i].port, messageBuffer);
}
